{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "21f9146a-c6f3-4a78-b3fb-0d262492e87c",
   "metadata": {},
   "source": [
    "# Lesson 3: Sentence Window Retrieval"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "541cadae-916c-42da-93ff-75d7f788ee8d",
   "metadata": {
    "height": 46
   },
   "outputs": [],
   "source": [
    "import warnings\n",
    "warnings.filterwarnings('ignore')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "78f48098-16d8-4209-b722-1ec6a0220c96",
   "metadata": {
    "height": 97
   },
   "outputs": [],
   "source": [
    "import utils\n",
    "\n",
    "import os\n",
    "import openai\n",
    "openai.api_key = utils.get_openai_api_key()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9ee55360-1fc4-41cc-bd49-82027797ea40",
   "metadata": {
    "height": 97,
    "tags": []
   },
   "outputs": [],
   "source": [
    "from llama_index import SimpleDirectoryReader\n",
    "\n",
    "documents = SimpleDirectoryReader(\n",
    "    input_files=[\"./eBook-How-to-Build-a-Career-in-AI.pdf\"]\n",
    ").load_data()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a12ada81-5c1c-47c9-b7b4-ba621a80bbcd",
   "metadata": {
    "height": 80,
    "tags": []
   },
   "outputs": [],
   "source": [
    "print(type(documents), \"\\n\")\n",
    "print(len(documents), \"\\n\")\n",
    "print(type(documents[0]))\n",
    "print(documents[0])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f2f662e4-3fb8-40c6-acc5-e8510348d113",
   "metadata": {
    "height": 80,
    "tags": []
   },
   "outputs": [],
   "source": [
    "from llama_index import Document\n",
    "\n",
    "document = Document(text=\"\\n\\n\".join([doc.text for doc in documents]))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "46ff01ea-b5b0-4e65-8565-b2444812bd84",
   "metadata": {},
   "source": [
    "## Window-sentence retrieval setup"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6a194b93-f975-4d38-babe-218d3aae6117",
   "metadata": {
    "height": 148,
    "tags": []
   },
   "outputs": [],
   "source": [
    "from llama_index.node_parser import SentenceWindowNodeParser\n",
    "\n",
    "# create the sentence window node parser w/ default settings\n",
    "node_parser = SentenceWindowNodeParser.from_defaults(\n",
    "    window_size=3,\n",
    "    window_metadata_key=\"window\",\n",
    "    original_text_metadata_key=\"original_text\",\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "88973de6-be8f-4653-aca3-8d0e884d9470",
   "metadata": {
    "height": 80,
    "tags": []
   },
   "outputs": [],
   "source": [
    "text = \"hello. how are you? I am fine!  \"\n",
    "\n",
    "nodes = node_parser.get_nodes_from_documents([Document(text=text)])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "860e40a8-6f50-4345-b314-c4d9349681c6",
   "metadata": {
    "height": 29,
    "tags": []
   },
   "outputs": [],
   "source": [
    "print([x.text for x in nodes])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b4334e02-c423-4555-8446-4794eadccd0a",
   "metadata": {
    "height": 29,
    "tags": []
   },
   "outputs": [],
   "source": [
    "print(nodes[1].metadata[\"window\"])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "03fea59c-ed0f-4cc8-87b4-871798edb094",
   "metadata": {
    "height": 80,
    "tags": []
   },
   "outputs": [],
   "source": [
    "text = \"hello. foo bar. cat dog. mouse\"\n",
    "\n",
    "nodes = node_parser.get_nodes_from_documents([Document(text=text)])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c329808d-248f-422e-bce0-1ac1ecba79a5",
   "metadata": {
    "height": 29,
    "tags": []
   },
   "outputs": [],
   "source": [
    "print([x.text for x in nodes])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cfd1eccb-823d-4f6f-8d6c-a9064dc76922",
   "metadata": {
    "height": 29,
    "tags": []
   },
   "outputs": [],
   "source": [
    "print(nodes[0].metadata[\"window\"])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fea97dda-8457-4f32-a2c7-26ae92eaf0b4",
   "metadata": {},
   "source": [
    "### Building the index"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "bb2387dd-6bdb-4d0d-98ea-2b468ddfe160",
   "metadata": {
    "height": 63,
    "tags": []
   },
   "outputs": [],
   "source": [
    "from llama_index.llms import OpenAI\n",
    "\n",
    "llm = OpenAI(model=\"gpt-3.5-turbo\", temperature=0.1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5102d65b-3584-4a25-88be-7d5dbc70c678",
   "metadata": {
    "height": 148,
    "tags": []
   },
   "outputs": [],
   "source": [
    "from llama_index import ServiceContext\n",
    "\n",
    "sentence_context = ServiceContext.from_defaults(\n",
    "    llm=llm,\n",
    "    embed_model=\"local:BAAI/bge-small-en-v1.5\",\n",
    "    # embed_model=\"local:BAAI/bge-large-en-v1.5\"\n",
    "    node_parser=node_parser,\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "fbf15398-25e9-47bd-b840-e57272d38683",
   "metadata": {
    "height": 97,
    "tags": []
   },
   "outputs": [],
   "source": [
    "from llama_index import VectorStoreIndex\n",
    "\n",
    "sentence_index = VectorStoreIndex.from_documents(\n",
    "    [document], service_context=sentence_context\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "55497194-35c9-4f91-85c4-ef6adb1aff78",
   "metadata": {
    "height": 63,
    "tags": []
   },
   "outputs": [],
   "source": [
    "sentence_index.storage_context.persist(persist_dir=\"./sentence_index\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ba4e7371-7a8e-451c-a5e7-e9e3d7371614",
   "metadata": {
    "height": 352,
    "tags": []
   },
   "outputs": [],
   "source": [
    "# This block of code is optional to check\n",
    "# if an index file exist, then it will load it\n",
    "# if not, it will rebuild it\n",
    "\n",
    "import os\n",
    "from llama_index import VectorStoreIndex, StorageContext, load_index_from_storage\n",
    "from llama_index import load_index_from_storage\n",
    "\n",
    "if not os.path.exists(\"./sentence_index\"):\n",
    "    sentence_index = VectorStoreIndex.from_documents(\n",
    "        [document], service_context=sentence_context\n",
    "    )\n",
    "\n",
    "    sentence_index.storage_context.persist(persist_dir=\"./sentence_index\")\n",
    "else:\n",
    "    sentence_index = load_index_from_storage(\n",
    "        StorageContext.from_defaults(persist_dir=\"./sentence_index\"),\n",
    "        service_context=sentence_context\n",
    "    )"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6b934879-e2c3-44f8-b98c-0300dc6389a9",
   "metadata": {},
   "source": [
    "### Building the postprocessor"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9f22f435-a90a-447c-9e63-8aebec1e968d",
   "metadata": {
    "height": 114,
    "tags": []
   },
   "outputs": [],
   "source": [
    "from llama_index.indices.postprocessor import MetadataReplacementPostProcessor\n",
    "\n",
    "postproc = MetadataReplacementPostProcessor(\n",
    "    target_metadata_key=\"window\"\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "77fad243-7ea8-4a81-b85e-91c3e638d932",
   "metadata": {
    "height": 97,
    "tags": []
   },
   "outputs": [],
   "source": [
    "from llama_index.schema import NodeWithScore\n",
    "from copy import deepcopy\n",
    "\n",
    "scored_nodes = [NodeWithScore(node=x, score=1.0) for x in nodes]\n",
    "nodes_old = [deepcopy(n) for n in nodes]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "946c4219-1d6e-4717-8c8f-40db659ea517",
   "metadata": {
    "height": 29,
    "tags": []
   },
   "outputs": [],
   "source": [
    "nodes_old[1].text"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "89900cb4-5d03-43f6-9d1d-de1350bfd299",
   "metadata": {
    "height": 29,
    "tags": []
   },
   "outputs": [],
   "source": [
    "replaced_nodes = postproc.postprocess_nodes(scored_nodes)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "72f98170-e368-461b-9939-5da80c4940e4",
   "metadata": {
    "height": 29,
    "tags": []
   },
   "outputs": [],
   "source": [
    "print(replaced_nodes[1].text)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "72aa4a50-5c0b-4f23-9eae-ec67cb701e29",
   "metadata": {},
   "source": [
    "### Adding a reranker"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "57608e01-ee96-4619-aab1-81d7fce1cbd1",
   "metadata": {
    "height": 148,
    "tags": []
   },
   "outputs": [],
   "source": [
    "from llama_index.indices.postprocessor import SentenceTransformerRerank\n",
    "\n",
    "# BAAI/bge-reranker-base\n",
    "# link: https://huggingface.co/BAAI/bge-reranker-base\n",
    "rerank = SentenceTransformerRerank(\n",
    "    top_n=2, model=\"BAAI/bge-reranker-base\"\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4d89da07-6b0c-477f-bd0a-2f466c9b159b",
   "metadata": {
    "height": 182,
    "tags": []
   },
   "outputs": [],
   "source": [
    "from llama_index import QueryBundle\n",
    "from llama_index.schema import TextNode, NodeWithScore\n",
    "\n",
    "query = QueryBundle(\"I want a dog.\")\n",
    "\n",
    "scored_nodes = [\n",
    "    NodeWithScore(node=TextNode(text=\"This is a cat\"), score=0.6),\n",
    "    NodeWithScore(node=TextNode(text=\"This is a dog\"), score=0.4),\n",
    "]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1adc6cb4-e741-480a-ab13-06e9194b13b2",
   "metadata": {
    "height": 63,
    "tags": []
   },
   "outputs": [],
   "source": [
    "reranked_nodes = rerank.postprocess_nodes(\n",
    "    scored_nodes, query_bundle=query\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3be841f6-0ab2-4c60-b52f-d27f81fcb1bb",
   "metadata": {
    "height": 29,
    "tags": []
   },
   "outputs": [],
   "source": [
    "print([(x.text, x.score) for x in reranked_nodes])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "74d51921-4b0b-4d89-963f-9a9e0ea439d9",
   "metadata": {},
   "source": [
    "### Runing the query engine"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "47bfe5a5-5ce1-4baa-ba61-3b39d16a2337",
   "metadata": {
    "height": 63,
    "tags": []
   },
   "outputs": [],
   "source": [
    "sentence_window_engine = sentence_index.as_query_engine(\n",
    "    similarity_top_k=6, node_postprocessors=[postproc, rerank]\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c4f166b9-99f9-4507-af59-3347eaf596c6",
   "metadata": {
    "height": 63,
    "tags": []
   },
   "outputs": [],
   "source": [
    "window_response = sentence_window_engine.query(\n",
    "    \"What are the keys to building a career in AI?\"\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ada6304b-a86c-4f6c-a2e9-8ac5b1091a58",
   "metadata": {
    "height": 63
   },
   "outputs": [],
   "source": [
    "from llama_index.response.notebook_utils import display_response\n",
    "\n",
    "display_response(window_response)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cbcbe614-befe-4bf7-9813-2c91899650e9",
   "metadata": {},
   "source": [
    "## Putting it all Together"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "313e8b1a-e9e7-4141-a4d2-72fb31a7e057",
   "metadata": {
    "height": 930
   },
   "outputs": [],
   "source": [
    "import os\n",
    "from llama_index import ServiceContext, VectorStoreIndex, StorageContext\n",
    "from llama_index.node_parser import SentenceWindowNodeParser\n",
    "from llama_index.indices.postprocessor import MetadataReplacementPostProcessor\n",
    "from llama_index.indices.postprocessor import SentenceTransformerRerank\n",
    "from llama_index import load_index_from_storage\n",
    "\n",
    "\n",
    "def build_sentence_window_index(\n",
    "    documents,\n",
    "    llm,\n",
    "    embed_model=\"local:BAAI/bge-small-en-v1.5\",\n",
    "    sentence_window_size=3,\n",
    "    save_dir=\"sentence_index\",\n",
    "):\n",
    "    # create the sentence window node parser w/ default settings\n",
    "    node_parser = SentenceWindowNodeParser.from_defaults(\n",
    "        window_size=sentence_window_size,\n",
    "        window_metadata_key=\"window\",\n",
    "        original_text_metadata_key=\"original_text\",\n",
    "    )\n",
    "    sentence_context = ServiceContext.from_defaults(\n",
    "        llm=llm,\n",
    "        embed_model=embed_model,\n",
    "        node_parser=node_parser,\n",
    "    )\n",
    "    if not os.path.exists(save_dir):\n",
    "        sentence_index = VectorStoreIndex.from_documents(\n",
    "            documents, service_context=sentence_context\n",
    "        )\n",
    "        sentence_index.storage_context.persist(persist_dir=save_dir)\n",
    "    else:\n",
    "        sentence_index = load_index_from_storage(\n",
    "            StorageContext.from_defaults(persist_dir=save_dir),\n",
    "            service_context=sentence_context,\n",
    "        )\n",
    "\n",
    "    return sentence_index\n",
    "\n",
    "\n",
    "def get_sentence_window_query_engine(\n",
    "    sentence_index, similarity_top_k=6, rerank_top_n=2\n",
    "):\n",
    "    # define postprocessors\n",
    "    postproc = MetadataReplacementPostProcessor(target_metadata_key=\"window\")\n",
    "    rerank = SentenceTransformerRerank(\n",
    "        top_n=rerank_top_n, model=\"BAAI/bge-reranker-base\"\n",
    "    )\n",
    "\n",
    "    sentence_window_engine = sentence_index.as_query_engine(\n",
    "        similarity_top_k=similarity_top_k, node_postprocessors=[postproc, rerank]\n",
    "    )\n",
    "    return sentence_window_engine"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3b5bb36f-97f9-4e03-bd4e-3ceb635a868b",
   "metadata": {
    "height": 148
   },
   "outputs": [],
   "source": [
    "from llama_index.llms import OpenAI\n",
    "\n",
    "index = build_sentence_window_index(\n",
    "    [document],\n",
    "    llm=OpenAI(model=\"gpt-3.5-turbo\", temperature=0.1),\n",
    "    save_dir=\"./sentence_index\",\n",
    ")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8d0e78e0-7765-4037-a5fa-63b711dab5aa",
   "metadata": {
    "height": 63
   },
   "outputs": [],
   "source": [
    "query_engine = get_sentence_window_query_engine(index, similarity_top_k=6)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "eb855430-8fcb-4c25-8d83-a30160449acf",
   "metadata": {},
   "source": [
    "## TruLens Evaluation"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "07aa5320-5c1c-4636-aa72-e6a786a58c8a",
   "metadata": {
    "height": 114
   },
   "outputs": [],
   "source": [
    "eval_questions = []\n",
    "with open('generated_questions.text', 'r') as file:\n",
    "    for line in file:\n",
    "        # Remove newline character and convert to integer\n",
    "        item = line.strip()\n",
    "        eval_questions.append(item)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7f9bdeb7-8c37-46b0-80f2-eb8ef1cc7b59",
   "metadata": {
    "height": 114
   },
   "outputs": [],
   "source": [
    "from trulens_eval import Tru\n",
    "\n",
    "def run_evals(eval_questions, tru_recorder, query_engine):\n",
    "    for question in eval_questions:\n",
    "        with tru_recorder as recording:\n",
    "            response = query_engine.query(question)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c7818a9b-04cc-4a6e-98ae-d3d0ab309998",
   "metadata": {
    "height": 97
   },
   "outputs": [],
   "source": [
    "from utils import get_prebuilt_trulens_recorder\n",
    "\n",
    "from trulens_eval import Tru\n",
    "\n",
    "Tru().reset_database()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "59ef4d27-2c4e-4dd0-85ed-a69a0f8eea00",
   "metadata": {},
   "source": [
    "### Sentence window size = 1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "93e6c651-838d-4c36-b806-efd0d8c2d5f0",
   "metadata": {
    "height": 131
   },
   "outputs": [],
   "source": [
    "sentence_index_1 = build_sentence_window_index(\n",
    "    documents,\n",
    "    llm=OpenAI(model=\"gpt-3.5-turbo\", temperature=0.1),\n",
    "    embed_model=\"local:BAAI/bge-small-en-v1.5\",\n",
    "    sentence_window_size=1,\n",
    "    save_dir=\"sentence_index_1\",\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8dc445f4-0311-4192-ac2e-4ea94b2653e5",
   "metadata": {
    "height": 63
   },
   "outputs": [],
   "source": [
    "sentence_window_engine_1 = get_sentence_window_query_engine(\n",
    "    sentence_index_1\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2750e2b1-ca60-495f-8168-33520740916c",
   "metadata": {
    "height": 80
   },
   "outputs": [],
   "source": [
    "tru_recorder_1 = get_prebuilt_trulens_recorder(\n",
    "    sentence_window_engine_1,\n",
    "    app_id='sentence window engine 1'\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ca942b16-52be-4107-861c-33daeca1f619",
   "metadata": {
    "height": 46
   },
   "outputs": [],
   "source": [
    "run_evals(eval_questions, tru_recorder_1, sentence_window_engine_1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "55dc7629-7fb7-4801-bd52-c9ab0da50267",
   "metadata": {
    "height": 29
   },
   "outputs": [],
   "source": [
    "Tru().run_dashboard()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "40d36ca4-8f52-4510-bfab-aa00b704d179",
   "metadata": {},
   "source": [
    "### Note about the dataset of questions\n",
    "- Since this evaluation process takes a long time to run, the following file `generated_questions.text` contains one question (the one mentioned in the lecture video).\n",
    "- If you would like to explore other possible questions, feel free to explore the file directory by clicking on the \"Jupyter\" logo at the top right of this notebook. You'll see the following `.text` files:\n",
    "\n",
    "> - `generated_questions_01_05.text`\n",
    "> - `generated_questions_06_10.text`\n",
    "> - `generated_questions_11_15.text`\n",
    "> - `generated_questions_16_20.text`\n",
    "> - `generated_questions_21_24.text`\n",
    "\n",
    "Note that running an evaluation on more than one question can take some time, so we recommend choosing one of these files (with 5 questions each) to run and explore the results.\n",
    "\n",
    "- For evaluating a personal project, an eval set of 20 is reasonable.\n",
    "- For evaluating business applications, you may need a set of 100+ in order to cover all the use cases thoroughly.\n",
    "- Note that since API calls can sometimes fail, you may occasionally see null responses, and would want to re-run your evaluations.  So running your evaluations in smaller batches can also help you save time and cost by only re-running the evaluation on the batches with issues."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6c55e4e1-5f1f-4c50-bd8e-4b54299377da",
   "metadata": {
    "height": 114
   },
   "outputs": [],
   "source": [
    "eval_questions = []\n",
    "with open('generated_questions.text', 'r') as file:\n",
    "    for line in file:\n",
    "        # Remove newline character and convert to integer\n",
    "        item = line.strip()\n",
    "        eval_questions.append(item)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e54c9977-c606-41bb-b87a-ec3cc17eabaf",
   "metadata": {},
   "source": [
    "### Sentence window size = 3"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0c8bea9d-9805-4302-b8c0-6e0f3eeec030",
   "metadata": {
    "height": 267
   },
   "outputs": [],
   "source": [
    "sentence_index_3 = build_sentence_window_index(\n",
    "    documents,\n",
    "    llm=OpenAI(model=\"gpt-3.5-turbo\", temperature=0.1),\n",
    "    embed_model=\"local:BAAI/bge-small-en-v1.5\",\n",
    "    sentence_window_size=3,\n",
    "    save_dir=\"sentence_index_3\",\n",
    ")\n",
    "sentence_window_engine_3 = get_sentence_window_query_engine(\n",
    "    sentence_index_3\n",
    ")\n",
    "\n",
    "tru_recorder_3 = get_prebuilt_trulens_recorder(\n",
    "    sentence_window_engine_3,\n",
    "    app_id='sentence window engine 3'\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e69d459e-e984-46b4-8fd9-ca4f77150647",
   "metadata": {
    "height": 46
   },
   "outputs": [],
   "source": [
    "run_evals(eval_questions, tru_recorder_3, sentence_window_engine_3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "dfdfb770",
   "metadata": {
    "height": 29
   },
   "outputs": [],
   "source": [
    "Tru().run_dashboard()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.13"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
